﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BreakOutBattles.Menus;
using BreakOutBattles.Game.Events;
using BreakOutBattles.Sound;
using BreakOutBattles.IO;
using System.Timers;
using System.Diagnostics;
using BreakOutBattles.IO.Net;
#endregion

namespace BreakOutBattles.Game
{
    #region Enums
    public enum MatchState
    {
        ROUND_ACTIVE,
        ROUND_OVER,
        ROUND_COUNTDOWN,
        MATCH_OVER
    }
    #endregion

    /// <summary>
    /// Renders and updates game screens and runs a simple match manager to handle different match states.
    /// </summary>
    public class GameManager
    {
        #region Fields
        private const int MAX_SCREENS = 2;
        private const double WAIT_TO_NEXT_ROUND = 5000;
        private const double WAIT_TO_TERMINATE = 5000;
        private const double COUNTDOWN_INTERVAL = 1000;

        private Dictionary<int, GameScreen> gameScreens;
        private Dictionary<int, Rectangle> screenAreas;

        private int countdownTicks = 3;
        private int currentScreen = 0;
        private int maxRounds = 2;
        private int playedRounds = 0;
        private int players = 0;
        private bool active = false;

        private Timer countdownTimer;
        private Timer nextRoundWaitTimer;
        private Timer terminateMatchTimer;
        private Stopwatch stopwatch;

        private IOManager ioManager;
        private Texture2D[] countDownTextures = new Texture2D[3];
        private Texture2D countDownTexture;
        private Texture2D screenSeparator;
        private Vector2 countDownPos;

        private Player lastWinner;
        private MatchState matchState = MatchState.ROUND_COUNTDOWN;

        private GameServer gameServer;
        private MatchConfiguration hostConfiguration;
        #endregion

        /// <summary>
        /// Creates a new GameManager.
        /// </summary>
        public GameManager()
        {
            Initialize();
        }

        #region Initialization
        /// <summary>
        /// Initializes the components of the game manager.
        /// </summary>
        private void Initialize()
        {
            ioManager = IOManager.Instance;
            gameServer = NetworkManager.Instance.GameServer;
            LoadContent();
            InitializeTimers();
            CreateGameScreens();
        }

        /// <summary>
        /// Loads the content to be used by the game manager.
        /// </summary>
        private void LoadContent()
        {
            countDownTextures[0] = ioManager.LoadTexture(ContentFolders.MISC, "1");
            countDownTextures[1] = ioManager.LoadTexture(ContentFolders.MISC, "2");
            countDownTextures[2] = ioManager.LoadTexture(ContentFolders.MISC, "3");
            screenSeparator = ioManager.LoadTexture(ContentFolders.MISC, "screenSeparator");
            countDownTexture = countDownTextures[2];

            countDownPos = new Vector2(640 - countDownTexture.Width / 2, 360 - countDownTexture.Height / 2);
        }

        /// <summary>
        /// Creates a fixed number of rectangles that the different game-screens
        /// can use to render it contents.
        /// </summary>
        private void CreateGameScreens()
        {
            screenAreas = new Dictionary<int, Rectangle>();
            screenAreas[0] = new Rectangle(0, 0, 640, 720);
            screenAreas[1] = new Rectangle(660, 0, 640, 720);

            gameScreens = new Dictionary<int, GameScreen>();
            gameScreens[0] = new GameScreen();
            gameScreens[1] = new GameScreen();
        }

        /// <summary>
        /// Intializes the timers that the game manager uses to control different
        /// events or other game related functionality at scheduled time intervals.
        /// </summary>
        private void InitializeTimers()
        {
            nextRoundWaitTimer = new Timer(WAIT_TO_NEXT_ROUND);
            nextRoundWaitTimer.Elapsed += ToNextRound;

            terminateMatchTimer = new Timer(WAIT_TO_TERMINATE);
            terminateMatchTimer.Elapsed += TerminateMatch;

            countdownTimer = new Timer(COUNTDOWN_INTERVAL);
            countdownTimer.Elapsed += CountDown;

            stopwatch = new Stopwatch();
        }
        #endregion

        #region Match Manager
        /// <summary>
        /// Checks the current match status. Evaluates if the game is over 
        /// or if another round is needed to take a winner.
        /// </summary>
        private void CheckMatchStatus()
        {
            if (playedRounds >= maxRounds && lastWinner.RoundsWon >= maxRounds)
            {
                matchState = MatchState.MATCH_OVER;
                terminateMatchTimer.Start();
            }
            else
            {
                matchState = MatchState.ROUND_OVER;
                nextRoundWaitTimer.Start();
            }
      
            stopwatch.Stop();
            NotifyRoundIsOver();
        }

        /// <summary>
        /// Handles the GameOver event fired from any of the managed game screens. Depending
        /// on the current game type, different actions will be taken when any player in the game
        /// is out of lifes.
        /// </summary>
        public void PlayerGameOverHandler(object sender, object args)
        {
            Player gameOverPlayer = args as Player;

            if (gameOverPlayer.PlayerIndex == 0)
            {
                lastWinner = gameScreens[1].Player;
                lastWinner.RoundsWon += 1;
                playedRounds++;
            }
            else
            {
                lastWinner = gameScreens[0].Player;
                lastWinner.RoundsWon += 1;
                playedRounds++;
            }

            CheckMatchStatus();
        }

        /// <summary>
        /// Handles the PlayerWin event, fired from any of the managed game screens
        /// when a player completed a level. Other players may still be alive.
        /// </summary>
        public void PlayerCompletedRound(object sender, object args)
        {
            Player roundWinner = args as Player;
            roundWinner.RoundsWon++;
            lastWinner = roundWinner;
            playedRounds++;

            CheckMatchStatus();
        }

        /// <summary>
        /// Notifies the game screens to terminate their work.
        /// </summary>
        private void NotifyTerminateMatch()
        {
            foreach (GameScreen gameScreen in gameScreens.Values)
            {
                gameScreen.Terminate();
            }
        }

        /// <summary>
        /// Notifies the game screens to reset it states before a new round
        /// begins.
        /// </summary>
        private void NotifyPrepareNextRound()
        {
            foreach (GameScreen gameScreen in gameScreens.Values)
            {
                gameScreen.PrepareNextRound();
            }
        }

        /// <summary>
        /// Notifies the game screens to do a specific action when the current round is over.
        /// </summary>
        private void NotifyRoundIsOver()
        {
            foreach (GameScreen gameScreen in gameScreens.Values)
            {
                gameScreen.HandleRoundOver();
            }
        }

        /// <summary>
        /// Starts a new match round.
        /// </summary>
        private void StartNextRound()
        {
            stopwatch.Start();
            countDownTexture = countDownTextures[2];
            matchState = MatchState.ROUND_ACTIVE;
            NotifyPrepareNextRound();
            SoundManager.PlayEffect(SoundEffects.ROUND_START);
        }

        /// <summary>
        /// Called by the count down timer. Decreases the count down value with
        /// one unit on each call. Reaching zero disables the count down timer.
        /// </summary>
        private void CountDown(object source, ElapsedEventArgs e)
        {
            SoundManager.PlayEffect(SoundEffects.POWER_UP_2);
            countdownTicks--;

            if (countdownTicks <= 0)
            {
                StartNextRound();
                countdownTimer.Stop();
                countdownTicks = 3;
            }
            else
            {
                countDownTexture = countDownTextures[countdownTicks - 1];
            }
        }

        /// <summary>
        /// Changes the current match state to ROUND_COUNTDOWN.
        /// </summary>
        private void ToNextRound(object source, ElapsedEventArgs e)
        {
            nextRoundWaitTimer.Stop();
            countdownTimer.Start();
            matchState = MatchState.ROUND_COUNTDOWN;
        }

        /// <summary>
        /// Terminates the current active match.
        /// </summary>
        private void TerminateMatch(object source, ElapsedEventArgs e)
        {
            terminateMatchTimer.Stop();
            SetMatchDetails();
            NotifyTerminateMatch();
            gameScreens.Clear();
            currentScreen = 0;

            GameState.CurrentState = GameStates.MATCH_REPORT;
            matchState = MatchState.ROUND_COUNTDOWN;
        }

        /// <summary>
        /// Sets the winner of the latest match, total time in the MatchDetails object.
        /// </summary>
        private void SetMatchDetails()
        {
            MatchReport.Winner = lastWinner;
            MatchReport.MatchTime = stopwatch.Elapsed;
        }   
        #endregion

        #region Update
        /// <summary>
        /// Updates all registered game screens.
        /// </summary>
        private void UpdateGameScreens(GameTime gameTime)
        {
            foreach (GameScreen gameScreen in gameScreens.Values)
            {
                gameScreen.Update(gameTime);
            }
        }

        /// <summary>
        /// Updates the game screens handled by the game manager.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            switch (matchState)
            {
                case MatchState.ROUND_ACTIVE:
                    UpdateGameScreens(gameTime);
                    break;

                default:
                    break;
            }
        }
        #endregion

        /// <summary>
        /// Attempts to create and add a new game screen and bounds it to a player.
        /// If the maximum number of game screens have been exceeded, no
        /// screen will be created.
        /// </summary>
        /// <param name="player">The owner of the screen to be created.</param>
        public void AddGameScreen(Player player)
        {
            if (currentScreen < MAX_SCREENS)
            {
                gameScreens[player.PlayerIndex] = new GameScreen(screenAreas[currentScreen], player);
                gameScreens[player.PlayerIndex].GameOver += PlayerGameOverHandler;
                gameScreens[player.PlayerIndex].LevelCompleted += PlayerCompletedRound;
            }
            currentScreen++;
        }

        public void SetGameScreen(Player player)
        {
            GameScreen playerScreen = gameScreens[player.PlayerIndex];
            playerScreen.GameArea = screenAreas[player.PlayerIndex];
            playerScreen.Player = player;

            playerScreen.Initialize();
        }

        /// <summary>
        /// Notifies the game screens to load a level. 
        /// </summary>
        /// <param name="levelToLoad">The name of the level to load.</param>
        private void NotifyLoadLevel(string levelToLoad)
        {
            if (!active)
            {
                foreach (GameScreen gameScreen in gameScreens.Values)
                {
                    gameScreen.LoadLevel(levelToLoad);
                }
            }
            countdownTimer.Start();
        }

        /// <summary>
        /// Gets and sets a flag indicating if the game manager is able to update and render
        /// the managed game screens.
        /// </summary>
        public bool Active
        {
            get { return active; }
            set { active = value; }
        }

        /// <summary>
        /// Renders the game screens handled by the game manager.
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(screenSeparator, Vector2.Zero, Color.White);

            foreach (GameScreen gameScreen in gameScreens.Values)
            {
                gameScreen.Draw(spriteBatch);
            }

            if (matchState == MatchState.ROUND_COUNTDOWN)
            {
                spriteBatch.Draw(countDownTexture, countDownPos, Color.White);
            }
        }
    }
}
